home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Graphics / Gnuplot / Source / GnuplotPlot.m < prev    next >
Encoding:
Text File  |  1993-03-18  |  18.5 KB  |  947 lines

  1.  
  2. static char RCSId[]="$Id: GnuplotPlot.m,v 1.1.1.1 1993/03/18 03:33:38 davis Exp $";
  3.  
  4.  
  5. #import <appkit/Application.h>
  6. #import <appkit/FontManager.h>
  7. #import <appkit/Pasteboard.h>
  8. #import <appkit/SavePanel.h>
  9. #import <appkit/Window.h>
  10. #import <appkit/publicWraps.h>    /* NXBeep()        */
  11.  
  12. #import <objc/List.h>
  13. #import <objc/NXStringTable.h>
  14.  
  15. #import <sys/stat.h>        /* stat()        */
  16. #import <sys/types.h>        /* stat()        */
  17.  
  18. #import <streams/streams.h>
  19.  
  20. #import <libc.h>        /* MAXPATHLEN        */
  21. #import <strings.h>        /* strlen(), strcpy()    */
  22. #import <stdlib.h>
  23.  
  24. #import "FunctionObject.h"
  25. #import "GnuplotPlot.h"
  26. #import "Gnuplot.h"
  27. #import "PlotView.h"
  28. #import "PopUpScrollView.h"
  29. #import "Status.h"
  30.  
  31.  
  32. @interface GnuplotPlot (Private)
  33.  
  34. - _saveWithNewName:(BOOL)doNewName retainNewName:(BOOL)doRetain;
  35. - _write:(const char *)filename;
  36. - _read:(const char *) filename;
  37.  
  38. @end
  39.  
  40.  
  41.  
  42. #define DEFAULT_WIN_WIDTH    380
  43. #define DEFAULT_WIN_HEIGHT    269
  44.  
  45.  
  46. extern int scanner (char expression[]);
  47. extern int almost_equals (int t_num, char *str);
  48. extern char input_line[];
  49.  
  50. extern int nextfe_color;        /* Is this a color machine?    */
  51.  
  52.  
  53. /*** Preferences -- Pseudo Class Variables ***/
  54.  
  55. static int constantUpdate = UPDATE_NOT3D;    /* Replot after any change? */
  56.                         /* Default window size    */
  57. static NXSize winSize = { DEFAULT_WIN_WIDTH, DEFAULT_WIN_HEIGHT };
  58.  
  59.  
  60.  
  61. /*
  62.  *  This function computes a new location for each new window created.  
  63.  *  p is the origin of the windows frame, w is the size of the window.
  64.  */
  65. static void _newLocation (NXPoint *p, NXSize w)
  66. {
  67.   static count = 0;            /* This tracks all our instances */
  68.  
  69.   p->x += (21.0 * count);
  70.   p->y -= (24.0 * count) + (w.height - DEFAULT_WIN_HEIGHT);
  71.   count = (count > 10)? 0 : count+1;
  72. }
  73.  
  74.  
  75.  
  76.  
  77. static char *endOfCommand (char *aString)
  78. {
  79.     char *cur;
  80.     char quote[200];
  81.     int numquotes = 0;
  82.  
  83.     for (cur = aString ; cur ; cur++)
  84.     switch (*cur) {
  85.     case '\n':
  86.     case '\0':
  87.         return cur;
  88.     case ';':
  89.         if (!numquotes)
  90.         return cur;
  91.     case '\'':
  92.     case '"':
  93.         if (numquotes && (quote[numquotes - 1] == *cur))    /* Close */
  94.         --numquotes;
  95.         else
  96.         quote [numquotes++] = *cur;            /* Open    */
  97.     }
  98.  
  99.     return NULL;
  100. }
  101.  
  102.  
  103.  
  104.  
  105. static BOOL isBadCommand (char *aString)
  106. {
  107.     char *end, was;
  108.     BOOL returnval = NO;
  109.  
  110.     if (aString && *aString) {
  111.  
  112.     /* Locate the end of the command */
  113.  
  114.     end = endOfCommand (aString);
  115.     was = *end;
  116.     *end = '\0';
  117.  
  118.     strcpy (input_line, aString);
  119.     scanner (aString);
  120.  
  121.     returnval = (almost_equals (0, "se$t")    &&
  122.  
  123.              (almost_equals (1, "o$utput") ||
  124.               almost_equals (1, "t$erminal")))    ||
  125.  
  126.             almost_equals (0, "pa$use");
  127.  
  128.     *end = was;
  129.  
  130.     }
  131.  
  132.     return returnval;
  133. }
  134.  
  135.  
  136. static void commandcat (char *dest, char *source)
  137. {
  138.     char *cursource, *curdest, *end;
  139.  
  140.     end = endOfCommand (source);
  141.  
  142.     curdest = dest + strlen (dest);
  143.     for (cursource = source ; cursource && (cursource != end); cursource++)
  144.     *(curdest++) = *cursource;
  145.  
  146.     *(curdest++) = '\n';
  147.     *curdest = '\0';
  148. }
  149.  
  150.  
  151. /*
  152.  *  stripCommands    Removes the 'set output "output.eps"\n', 'set 
  153.  *            terminal ...\n', and 'pause ...' lines and 
  154.  *            comments from the string.
  155.  */
  156. static char *stripCommands (char **aString)
  157. {
  158.     char *cur, *newText;
  159.  
  160.     if (aString && *aString) {
  161.     cur = *aString;
  162.     newText = (char *) malloc (strlen (*aString));
  163.  
  164.     *newText = '\0';
  165.  
  166.     while (cur && *cur) {
  167.         if (!isBadCommand (cur))
  168.         commandcat (newText, cur);
  169.  
  170.         cur = endOfCommand (cur);
  171.         cur = (cur ? cur + 1 : NULL);
  172.     }
  173.  
  174.     strcpy (*aString, newText);
  175.     return *aString;
  176.     }
  177.  
  178.     return NULL;
  179. }
  180.  
  181.  
  182.  
  183. @implementation GnuplotPlot
  184.  
  185.  
  186. + initialize
  187. {
  188.     /*  
  189.      *  This is probably temporary -- until color is handled better, 
  190.      *  this just checks to see if we're on a color machine and sets 
  191.      *  the color variable of the terminal accordingly.
  192.      */
  193.     nextfe_color = (([NXApp colorScreen]->depth) == NX_TwoBitGrayDepth)? 0 : 1;
  194.     return self;
  195. }
  196.  
  197.  
  198.  
  199. + setConstantUpdate:(int)updateType
  200. {
  201.     constantUpdate = updateType;
  202.     return self;
  203. }
  204.  
  205.  
  206. + setHalvePlot:(BOOL)condition
  207. {
  208.     if (condition) {
  209.     winSize.width = 380;
  210.     winSize.height = 269;
  211.     } else {
  212.     winSize.width = 740;
  213.     winSize.height = 521;
  214.     }
  215.  
  216.     return [Status setHalvePlot:condition];
  217. }
  218.  
  219.  
  220. - init
  221. {
  222.     return [self initFromFile: NULL];
  223. }
  224.  
  225.  
  226. - initFromFile:(const char *) filename
  227. {
  228.     NXRect theFrame;
  229.     const char *validSendTypes[2];
  230.  
  231.     [super init];
  232.     controller = [NXApp delegate];
  233.     zone = [self zone];
  234.  
  235.     [NXApp loadNibSection: "GnuplotPlot.nib"
  236.             owner: self
  237.         withNames: NO
  238.          fromZone: zone];
  239.  
  240.     status = [[[Status allocFromZone:zone] init] setDelegate: self];
  241.  
  242.     [plotScrollView setVertScrollerRequired: YES];
  243.     [plotScrollView setHorizScrollerRequired: YES];
  244.     [plotScrollView setBorderType:NX_BEZEL];
  245.  
  246.  
  247.     plotView = [[PlotView allocFromZone:zone] initFrame:NULL];
  248.     [[plotScrollView setDocView:plotView] free];
  249.  
  250.     stringSet = [controller stringSet];
  251.     fullPath = NULL;
  252.  
  253.     /* Setup services */
  254.     validSendTypes[0] = NXFilenamePboardType;
  255.     validSendTypes[1] = NULL;
  256.     [NXApp registerServicesMenuSendTypes: validSendTypes andReturnTypes: NULL];
  257.  
  258.     [window sizeWindow:winSize.width :winSize.height];    /* Default size    */
  259.  
  260.     [window getFrame:&theFrame];        /* Position the window    */
  261.     _newLocation (&theFrame.origin, winSize);
  262.     [window moveTo:NX_X(&theFrame) :NX_Y(&theFrame)];
  263.  
  264.     [window setMiniwindowIcon: "GnuplotDoc.tiff"];
  265.  
  266.     if (filename)  {
  267.     [self setName:filename];
  268.     isTitled = YES;
  269.     [window makeKeyAndOrderFront:self];
  270.     if (![self plotFromFile:filename]) {
  271.         [NXApp delayedFree:self];
  272.         return nil;
  273.     }
  274.     } else {
  275.     [window makeKeyAndOrderFront:self];
  276.     [self setName: NULL];
  277.     isTitled = NO;
  278.     }
  279.  
  280.     [self setDocEdited: NO];
  281.  
  282.     return self;
  283. }
  284.  
  285.  
  286. - free
  287. {
  288.     NXZoneFree (zone, fullPath);
  289.     NXZoneFree (zone, readText);
  290.  
  291.     [status free];
  292.  
  293.     if (window)
  294.     [window free];
  295.  
  296.     return [super free];
  297. }
  298.  
  299.  
  300.  
  301. /*** Target/Action ***/
  302.  
  303.  
  304. - save:sender
  305. {
  306.     return [self _saveWithNewName:NO retainNewName:YES];
  307. }
  308.  
  309.  
  310. - saveAs:sender
  311. {
  312.     return [self _saveWithNewName:YES retainNewName:YES];
  313. }
  314.  
  315.  
  316. - saveTo:sender
  317. {
  318.     return [self _saveWithNewName:YES retainNewName:NO];
  319. }
  320.  
  321.  
  322. - revertToSaved:sender
  323. {
  324.     switch (NXRunAlertPanel ([stringSet valueForStringKey: "revertT"],
  325.                  [stringSet valueForStringKey: "revert"],
  326.                  [stringSet valueForStringKey: "revertB"],
  327.                  [stringSet valueForStringKey: "cancelB"],
  328.                  NULL, fullPath)) {
  329.     case NX_ALERTDEFAULT:
  330.     [status resetCurrent];
  331.     [self plotFromFile:fullPath];
  332.     [self setDocEdited:NO];
  333.  
  334.     /* 
  335.      * Make sure any panels that are showing info about this 
  336.      * document know that we just changed.
  337.      */
  338.     [controller updateApp];
  339.         [[FontManager new] setSelFont:[status font] isMultiple:NO];
  340.  
  341.     return self;
  342.     break;
  343.     case NX_ALERTOTHER:
  344.     return nil;
  345.     break;
  346.     }
  347.  
  348.     return self;
  349. }
  350.  
  351.  
  352.  
  353. - close:sender
  354. {
  355.     [window performClose:self];
  356.     return self;
  357. }
  358.  
  359.  
  360. - print:sender;
  361. {
  362.     return [plotView printPSCode:sender];
  363. }
  364.  
  365.  
  366.  
  367. - setName: (const char *) name
  368. {
  369.     char title[255];
  370.     char *oldFullPath = fullPath;
  371.  
  372.     if (name) {
  373.  
  374.     fullPath = NXCopyStringBufferFromZone (name, zone);
  375.     [window setTitleAsFilename:fullPath];
  376.     isTitled = YES;
  377.  
  378.     } else {
  379.  
  380.     sprintf (title, "%s%d", [stringSet valueForStringKey: "untitled"],
  381.                             [controller numberNew]);
  382.     fullPath = NXCopyStringBufferFromZone (title, zone);
  383.     [window setTitle: title];
  384.     isTitled = NO;
  385.  
  386.     }
  387.  
  388.     NXZoneFree (zone, oldFullPath);
  389.  
  390.     return self;
  391. }
  392.  
  393.  
  394. - (const char *) name
  395. {
  396.     return fullPath;
  397. }
  398.  
  399.  
  400. - setCurrentFont:aFont
  401. {
  402.     if (aFont)  {
  403.     [status setFont:aFont];
  404.     }
  405.  
  406.     return self;
  407. }
  408.  
  409.  
  410. - currentFont
  411. {
  412.     return [status font];
  413. }
  414.  
  415.  
  416.  
  417. - window
  418. {
  419.     return window;
  420. }
  421.  
  422.  
  423. - status
  424. {
  425.     return status;
  426. }
  427.  
  428.  
  429. - plotFromFile:(const char *)aFullPath
  430. {
  431.     id returnVal = self;
  432.  
  433.     if (![self _read:aFullPath])    /* _read into readText        */
  434.     return nil;
  435.  
  436.     stripCommands (&readText);        /* Ignore output, terminal, and    */
  437.                     /* multiple plot commands    */
  438.  
  439.     [status setAppendix: readText];
  440.  
  441.     /* 
  442.      *  We don't check the return status of plot: because even if 
  443.      *  there are errors, we want the file to remain open -- maybe the 
  444.      *  user can correct the errors without having to use vi.
  445.      */
  446.     [self plot:self];
  447.     [status grabCurrent];
  448.  
  449.  
  450.     [status setAppendix: NULL];
  451.     NXZoneFree (zone, readText);
  452.     readText = NULL;
  453.  
  454.     return returnVal;
  455. }
  456.  
  457.  
  458. - plot:sender
  459. {
  460.     id returnVal = self;
  461.  
  462.     /* 
  463.      *  We don't return nil if the settings don't allow us to create a 
  464.      *  valid plot -- we simply don't plot.
  465.      */
  466.     if (![status canPlot]) {
  467.  
  468.     [plotView newStream:NULL];
  469.  
  470.     } else {
  471.  
  472.     char *newTitle;
  473.     char *oldTitle = NXCopyStringBufferFromZone ([window title], zone);
  474.  
  475.     newTitle = NXZoneMalloc (zone, strlen (oldTitle) + 12);
  476.     sprintf (newTitle, [stringSet valueForStringKey:"plotting..."],
  477.          oldTitle);
  478.     [window setTitle:newTitle];
  479.     NXPing();
  480.  
  481.     /* 
  482.      *  We do, however, report an error if the settings do allow 
  483.      *  us to plot but we were unable to (e.g. if gnuplot bailed 
  484.      *  out).
  485.      */
  486.     if (![status plot]) {
  487.         [self reportScriptError:self];
  488.         returnVal = nil;
  489.     } else {
  490.  
  491.         [plotView newStream:[status stream]];
  492.  
  493.         /* 
  494.          * Make sure the scale of the doc matches the current 
  495.          * popUp setting
  496.          */
  497.         [plotScrollView update];    /* Not done at every window update */
  498.  
  499.         [window update];
  500.     }
  501.  
  502.     [window setTitle:oldTitle];
  503.     NXPing();
  504.     NXZoneFree (zone, oldTitle);
  505.     NXZoneFree (zone, newTitle);
  506.     }
  507.  
  508.     return returnVal;
  509. }
  510.  
  511.  
  512.  
  513.  
  514. - reportScriptError: sender;
  515. {
  516.     NXRunAlertPanel ([stringSet valueForStringKey:"scriptErrorT"],
  517.              [stringSet valueForStringKey:"scriptError"],
  518.              NULL, NULL, NULL, [self name]);
  519.  
  520.     [plotView newStream: NULL];
  521.     [window display];
  522.     return self;
  523. }
  524.  
  525.  
  526. - addDataFile:(const char *)aPath
  527. {
  528.     FunctionObject *functionObject;
  529.  
  530.     if (![FunctionObject isAcceptableDataFile:aPath])
  531.     return nil;
  532.  
  533.     functionObject = [[FunctionObject allocFromZone: [status zone]]
  534.                                      initFromString: aPath];
  535.     [functionObject setDataFile:YES];
  536.     [[status functions] addObject:functionObject];
  537.     [status reportSettingsChange:self];
  538.  
  539.     return self;
  540. }
  541.  
  542.  
  543.  
  544. - setDocEdited:(BOOL) state
  545. {
  546.     if (((constantUpdate == UPDATE_ALWAYS) ||
  547.      ((constantUpdate == UPDATE_NOT3D) && ![status isThreeD])) && state)
  548.     [self plot:self];
  549.  
  550.     [window setDocEdited:state];
  551.  
  552.     return self;
  553. }
  554.  
  555. - (BOOL) isDocEdited
  556. {
  557.     return [window isDocEdited];
  558. }
  559.  
  560.  
  561.  
  562. - hideDocument:sender
  563. {
  564.   [window orderOut:sender];
  565.   return self;
  566. }
  567.  
  568.  
  569.  
  570.  
  571. /*** Services ***/
  572.  
  573.  
  574. - validRequestorForSendType:(NXAtom)sendType andReturnType:(NXAtom)returnType
  575. /*
  576.  *  Services menu support.
  577.  *  We are a valid requestor if the send type is filename and there is 
  578.  *  no return data from the request.
  579.  */
  580. {
  581.     return (isTitled && (sendType == NXFilenamePboardType) &&
  582.         (!returnType || !*returnType)) ? self : nil;
  583. }
  584.  
  585. - (BOOL)writeSelectionToPasteboard:pboard types:(NXAtom *)types
  586. /*
  587.  *  Services menu support.
  588.  *  Here we are asked by the Services menu mechanism to supply the 
  589.  *  filename (which we said we were a valid requestor for in the above 
  590.  *  method).
  591.  */
  592. {
  593.     int save;
  594.  
  595.     if (isTitled) {
  596.         while (types && *types)
  597.         if (*types == NXFilenamePboardType)
  598.         break;
  599.         else 
  600.         types++;
  601.  
  602.         if (types && *types) {
  603.             if ([self isDocEdited]) {
  604.         save = NXRunAlertPanel (
  605.             [stringSet valueForStringKey:"servicesT"],
  606.             [stringSet valueForStringKey:"serviceSave"],
  607.             [stringSet valueForStringKey:"save"],
  608.             [stringSet valueForStringKey:"dont save"], NULL);
  609.  
  610.                 if (save == NX_ALERTDEFAULT)
  611.             [self save:self];
  612.             }
  613.             [pboard declareTypes:&NXFilenamePboardType num:1 owner:self];
  614.             [pboard writeType:NXFilenamePboardType
  615.                      data:fullPath length:strlen (fullPath)+1];
  616.             return YES;
  617.         }
  618.     }
  619.  
  620.     return NO;
  621. }
  622.  
  623.  
  624.  
  625. /*** Autoupdate Methods ***/
  626.  
  627. - (BOOL)validateCommand:menuCell
  628. {
  629.     SEL action = [menuCell action];
  630.     BOOL edited = [self isDocEdited];
  631.     BOOL canPlot = [status canPlot];
  632.  
  633.     /* 
  634.      *  Note that we only allow a document to be saved when the 
  635.      *  settings validly produce a plot.  Also, if we need to see if 
  636.      *  the plot has a file with which it is associated.
  637.      */
  638.     
  639.     if (action == @selector(save:))
  640.         return (edited || !isTitled) && canPlot;
  641.     else if (action == @selector(saveAs:))
  642.         return canPlot;
  643.     else if (action == @selector(saveTo:))
  644.         return canPlot;
  645.     else if (action == @selector(saveAll:))    /* See Gnuplot class */
  646.         return (edited || !isTitled) && canPlot;
  647.     else if (action == @selector(revertToSaved:))
  648.     return (edited && isTitled);
  649.     else if (action == @selector(print:))
  650.     return canPlot;
  651.     else if (action == @selector(plot:))
  652.     return canPlot;
  653.  
  654.     return YES;
  655. }
  656.     
  657.  
  658.  
  659.  
  660. /*** Window Delegate Methods ***/
  661.  
  662. - windowDidBecomeMain:sender
  663. {
  664.     /* Update font panel */
  665.     if (plotView)  {
  666.     [window makeFirstResponder: plotView];
  667. //    [[FontManager new] setSelFont:[status font] isMultiple:NO];
  668.     }
  669.  
  670.     [controller setCurrentDoc:self];
  671.  
  672.     return self;
  673. }
  674.  
  675.  
  676.  
  677. - windowDidMiniaturize:sender
  678. {
  679.     [controller setCurrentDoc:nil];
  680.     return self;
  681. }
  682.  
  683.  
  684.  
  685. - windowDidResignMain:sender
  686. {
  687. //    [[FontManager new] setEnabled:NO];
  688.     return self;
  689. }
  690.  
  691.  
  692.  
  693. - windowDidResize:sender
  694. {
  695.     int aTag;
  696.  
  697.     /*
  698.      *  Make sure the scale of the doc matches the current popUp 
  699.      *  setting.  (If it's set to "Always Fit," which is 0, then resizing 
  700.      *  window should cause it to rescale.)
  701.      */
  702.     aTag = [plotScrollView currentTag];
  703.     if (!aTag)
  704.     [plotScrollView setCurrentTag: aTag];
  705.  
  706.     return self;
  707. }
  708.  
  709.  
  710. - windowDidUpdate:sender
  711. {
  712.     [[FontManager new] setEnabled:([status canPlot] && [window isMainWindow])];
  713.     return self;
  714. }
  715.  
  716.  
  717.  
  718. - windowWillClose:sender
  719. {
  720.     id returnVal = self;
  721.     BOOL done = NO;
  722.  
  723.     if ([self isDocEdited])  {
  724.  
  725.     while (!done)  {    /* Keep asking until user tells what to do */
  726.  
  727.         switch (NXRunAlertPanel([stringSet valueForStringKey:"willCloseT"],
  728.                     [stringSet valueForStringKey:"willClose"],
  729.                     [stringSet valueForStringKey:"save"],
  730.                     [stringSet valueForStringKey:"dont save"],
  731.                     [stringSet valueForStringKey:"cancelB"],
  732.                     [self name]))  {
  733.         case NX_ALERTDEFAULT:        /* Save        */
  734.         if (returnVal = [self save:self])
  735.             done = YES;
  736.         break;
  737.         case NX_ALERTALTERNATE:
  738.         returnVal = self;        /* Don't Save    */
  739.         done = YES;
  740.         break;
  741.         case NX_ALERTOTHER:            /* Cancel    */
  742.         returnVal = nil;
  743.         done = YES;
  744.         break;
  745.  
  746.         }
  747.  
  748.     }
  749.  
  750.     }
  751.  
  752.     if (returnVal)  {
  753.     plotScrollView = nil;        /* Don't want stale instance vars */
  754.     plotView = nil;
  755.  
  756.     [window orderOut:self];
  757.     [controller docDidClose:self];
  758.     [NXApp delayedFree: self];
  759.     }
  760.  
  761.     return returnVal;
  762. }
  763.  
  764.  
  765. - windowWillResize:sender toSize:(NXSize *) frameSize
  766. {
  767.     NXRect rect;
  768.     float x, y, w, h;
  769.  
  770.     [plotView getFrame:&rect];
  771.  
  772.     /*  
  773.      *  If the frame rectangle of the view has both a length and a 
  774.      *  width, i.e. if there's something to see, we control the sizing 
  775.      *  so it can never be bigger than the rectangle (except to allow 
  776.      *  room for border and sliders).
  777.      */
  778.  
  779.     if ((w = NX_WIDTH(&rect)) && (h = NX_HEIGHT(&rect)))  {
  780.  
  781.     x = w + 22;            /* Make room for borders & sliders */
  782.     y = h + 49;
  783.  
  784.             /* If the current setting isn't "Always Fit"    */
  785.     if ([plotScrollView currentTag])  {
  786.  
  787.         if (frameSize->width > x) frameSize->width = x;
  788.         if (frameSize->height > y) frameSize->height = y;
  789.  
  790.     }
  791.  
  792.     }
  793.  
  794. /*
  795.  *  Don't allow the window to get small enough to lose the scroll 
  796.  *  buttons and the pop-up list.
  797.  */
  798.  
  799.     if (frameSize->width < 147) frameSize->width = 147;
  800.     if (frameSize->height < 65) frameSize->height = 65;
  801.  
  802.     return self;
  803. }
  804.  
  805.  
  806.  
  807. /*** Status Delegate Methods ***/
  808.  
  809.  
  810. - settingsDidChange:sender
  811. {
  812.     [self setDocEdited: YES];
  813.     return self;
  814. }
  815.  
  816.  
  817.  
  818. // Shuts up the compiler about unused RCSId
  819. - (const char *) rcsid
  820. {
  821.     return RCSId;
  822. }
  823.  
  824.  
  825. @end
  826.  
  827.  
  828.  
  829. @implementation GnuplotPlot (Private)
  830.  
  831.  
  832. /*
  833.  *  This method was taken from the NeXTSTEP Developer Example 
  834.  *  WhatsUpDoc.  All varieties of save go through this routine.  It 
  835.  *  covers all the cases of running the Save Panel and retaining the 
  836.  *  name chosen.
  837.  */
  838. - _saveWithNewName:(BOOL)doNewName retainNewName:(BOOL)doRetain
  839. {
  840.     id savePanel;
  841.     const char *saveName;        /* filename to save into */
  842.  
  843.     if (doNewName || !isTitled) {    /* saveAs or saveTo */
  844.  
  845.     savePanel = [SavePanel new];
  846.     [savePanel setRequiredFileType:DOCUMENT_TYPE];
  847.     if ([savePanel runModalForDirectory:fullPath
  848.                file: (isTitled ? rindex (fullPath, '/') + 1 
  849.                           : [self name])]) {
  850.         saveName = [savePanel filename];
  851.     } else
  852.         return self;        /* aborted out? */
  853.  
  854.  
  855.     } else {                /* ordinary Save */
  856.  
  857.     /* 
  858.      *  In order to save a document, it must be able to produce a 
  859.      *  valid plot and it must either be edited or be untitled.
  860.      */
  861.     if ((![self isDocEdited] && isTitled) || ![status canPlot])
  862.         return nil;
  863.  
  864.     saveName = fullPath;
  865.     }
  866.  
  867.     if (![self _write:saveName])  {
  868.     NXRunAlertPanel ([stringSet valueForStringKey: "Save"],
  869.              [stringSet valueForStringKey: "cantSave"],
  870.              [stringSet valueForStringKey: "OKB"],
  871.              NULL, NULL, saveName);
  872.     return nil;
  873.     }
  874.                     /* Update the document name    */
  875.     if (doRetain) {            /* if requested            */
  876.     [self setName:saveName];
  877.     [self setDocEdited:NO];
  878.     }
  879.  
  880.     return self;
  881. }
  882.  
  883.  
  884.  
  885. /*
  886.  *  This method does all the writing so any application-specific stuff 
  887.  *  should go here.
  888.  */
  889. - _write:(const char *)filename
  890. {
  891.     NXModalSession *session;
  892.     id modalPanel = NXGetAlertPanel ("Save", "Replotting %s.",
  893.                      NULL,NULL,NULL, [self name]);
  894.  
  895.     if ([Status lastplot] != status)  {
  896.     session = [NXApp beginModalSession:NULL for:modalPanel];
  897.     [self plot:self];
  898.     [NXApp endModalSession:session];
  899.     NXFreeAlertPanel(modalPanel);
  900.     }
  901.  
  902.     return [status saveToFile:filename];
  903. }
  904.  
  905.  
  906.  
  907.  
  908. - _read:(const char *) filename
  909. {
  910.     NXStream *stream;
  911.     struct stat fileinfo;
  912.  
  913.     if (stat (filename, &fileinfo)) {
  914.     NXRunAlertPanel ([stringSet valueForStringKey: "openT"],
  915.              [stringSet valueForStringKey: "cantOpen"],
  916.              [stringSet valueForStringKey: "OKB"],
  917.              NULL, NULL, filename);
  918.     return nil;
  919.     }
  920.  
  921.     if (stream = NXMapFile (filename, NX_READONLY)) {
  922.     if (readText)
  923.         NXZoneFree (zone, readText);
  924.     readText = NXZoneMalloc (zone, fileinfo.st_size + 1);
  925.     NXScanf (stream, "%[^\0]", readText);
  926.     NXClose (stream);
  927.  
  928.     /* Check to see if the file we read is writable -- foreshadowing */
  929.         if (!(fileinfo.st_mode & S_IWRITE))
  930.         NXRunAlertPanel ([stringSet valueForStringKey: "warningT"],
  931.                  [stringSet valueForStringKey: "unwritable"],
  932.                  [stringSet valueForStringKey: "OKB"],
  933.                  NULL, NULL, filename);
  934.     } else {
  935.     NXRunAlertPanel ([stringSet valueForStringKey: "openT"],
  936.              [stringSet valueForStringKey: "cantOpen"],
  937.              [stringSet valueForStringKey: "OKB"],
  938.              NULL, NULL, filename);
  939.     return nil;
  940.     }
  941.  
  942.     return self;
  943. }
  944.  
  945.  
  946. @end
  947.